package com.bjsxt.service.impl;

import com.bjsxt.contant.LoginConstant;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * description:
 * 自己创建 UserDetailService 接口实现类
 * TODO: 这里在进行登录的时候如果用户或者密码不正确那么不会进行处理
 *
 * @author ALIENWARE <br />
 * @date 2021/6/23
 */
@Service
public class MyUserDetailServiceImpl implements UserDetailsService {

    /**
     * JDBC 操作客户端类
     */
    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 登录的实现
     *
     * @param userName 用户名称
     * @return UserDetails 对象
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        // 获取请求数据的对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // 获取登录类型数据
        String loginType = requestAttributes.getRequest().getParameter("login_type");
        // 如果值是空的值
        if (StringUtils.isBlank(loginType)) throw new AuthenticationServiceException("请添加 login_type 参数");
        // 创建 UserDetails 对象
        UserDetails userDetails;
        try {
            // 因为开启了 refresh_token 机制 ,在刷新 token 的时候 jwt 使用的是 username 进行查询 , 而我们将 userName 的值变成了用户的 id 所以我们需要在这里进行纠正 userName 的值变成响应的 id 值才能正常的登录
            String grant_type = requestAttributes.getRequest().getParameter("grant_type");
            // 如果 grant_type 等于 REFRESH_TOKEN 那么就进行数据的纠正 , 将 userName(id) --> userName
            if (LoginConstant.REFRESH_TOKEN.equals(grant_type.toUpperCase()))
                userName = adjustUserName(userName, loginType);
            // 进行登录类型的判断
            switch (loginType) {
                // 类型是管理员
                case LoginConstant.ADMIN_TYPE:
                    userDetails = loadAdminUserByUsername(userName);
                    break;
                // 类型是用户
                case LoginConstant.MEMBER_TYPE:
                    userDetails = loadMemberUserByUsername(userName);
                    break;
                default:
                    throw new AuthenticationServiceException("暂不支持的登录方式" + loginType);
            }
        } catch (IncorrectResultSizeDataAccessException e) { // 处理 sql 查询方法名称没找到抛出的异常
            e.printStackTrace();
            // 抛出用户名不存在异常
            throw new UsernameNotFoundException("用户名" + userName + "不存在");
        }
        // 返回 UserDetails 对象
        return userDetails;
    }

    /**
     * 用户类型登录的方法
     *
     * @param userName 用户名称
     * @return UserDetails 对象
     */
    private UserDetails loadMemberUserByUsername(String userName) {
        // 进行会员用户的查询
        return jdbcTemplate.queryForObject(LoginConstant.QUERY_MEMBER_SQL, (resultSet, rowNum) -> {
            // 如果查询的数据是空 直接返回不存在
            if (resultSet.wasNull()) throw new UsernameNotFoundException("会员" + userName + "不存在");
            // 拿出用户的 id
            long id = resultSet.getLong("id");
            // 拿出用户的密码
            String password = resultSet.getString("password");
            // 拿出用户的状态
            int status = resultSet.getInt("status");
            // 创建一个用户对象 , 这里的用户名称推荐写 id 以后在拿出对象的时候查询本条数据就会方便许多, 不会出现重名的用户拿不到的现象
            return new User(String.valueOf(id), password, status == 1, true, true, true, Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
        }, userName, userName);
    }

    /**
     * 管理员用户登录的方法
     * 1. 使用用户名进行用户的查询
     * 2. 查询这个用户对应的权限
     * 3. 封装成 UserDetails 对象并返回
     *
     * @param userName 用户名称
     * @return UserDetails 对象
     */
    private UserDetails loadAdminUserByUsername(String userName) {
        // 进行用户的查询
        return jdbcTemplate.queryForObject(LoginConstant.QUERY_ADMIN_SQL, (resultSet, rowNum) -> {
            // 如果查询的数据是空 直接返回不存在
            if (resultSet.wasNull()) throw new UsernameNotFoundException("用户名" + userName + "不存在");
            // 拿出用户的 id
            long id = resultSet.getLong("id");
            // 拿出用户的密码
            String password = resultSet.getString("password");
            // 拿出用户的状态
            int status = resultSet.getInt("status");
            // 创建一个用户对象 , 这里的用户名称推荐写 id 以后在拿出对象的时候查询本条数据就会方便许多, 不会出现重名的用户拿不到的现象
            return new User(String.valueOf(id), password, status == 1, true, true, true, getUserPermissions(id));
        }, userName);
    }

    /**
     * 根据用户 id 查询用户对应的权限
     *
     * @param id 用户 id
     * @return 权限集合
     */
    private Collection<? extends GrantedAuthority> getUserPermissions(long id) {
        // 查询当前用户的权限
        String code = jdbcTemplate.queryForObject(LoginConstant.QUERY_ROLE_CODE_SQL, String.class, id);
        // 权限集合
        List<String> permissions;
        // code == 管理员代码
        if (LoginConstant.ADMIN_ROLE_CODE.equals(code))
            // 查询全部的权限
            permissions = jdbcTemplate.queryForList(LoginConstant.QUERY_ALL_PERMISSIONS, String.class);
            // 不是管理员
        else
            // 查询当前用户对应的权限
            permissions = jdbcTemplate.queryForList(LoginConstant.QUERY_PERMISSION_SQL, String.class, id);
        // 如果用户的权限是空, 返回空集合
        if (permissions == null || permissions.isEmpty()) return Collections.EMPTY_LIST;
        return permissions
                // 流转换
                .stream()
                // 去重
                .distinct()
                // 进行对应实现类的转换
                .map(perm -> new SimpleGrantedAuthority(perm))
                // 返回对应的集合
                .collect(Collectors.toSet());
    }

    /**
     * 用户 REFRESH_TOKEN 的时候进行数据 id 转换成为 userName 的纠正
     *
     * @param id        用户 id
     * @param loginType 登录类型
     * @return 用户名称
     */
    private String adjustUserName(String id, String loginType) {
        // 如果是管理员原进行管理员数据 id 的纠正
        if (LoginConstant.ADMIN_TYPE.equals(loginType)) {
            // 进行管理员名称数据的查询
            return jdbcTemplate.queryForObject(LoginConstant.QUERY_ADMIN_USER_WITH_ID, String.class, id);
        } else if (LoginConstant.MEMBER_TYPE.equals(loginType)) { // 如果是用户/会员原进行管理员数据 id 的纠正
            // 进行用户会员名称数据的查询
            return jdbcTemplate.queryForObject(LoginConstant.QUERY_MEMBER_USER_WITH_ID, String.class, id);
        }
        // 直接返回原值
        return id;
    }
}
